#pragma once

#include "znx_event.h"
#include "comm/znx_rwlock.h"
#include "comm/znx_inet.h"
#include "comm/znx_queue.h"
#include "comm/znx_log.h"


typedef struct znx_listener_s           znx_listener_t;

struct znx_listener_s {
    const char                          *listen_addr;
    int                                 backlog;
    // SOCK_STREAM or SOCK_DGRAM
    int                                 type;
    unsigned                            nodelay:1;
    unsigned                            quickack:1;
    unsigned                            reuseport:1;
    unsigned                            reuseaddr:1;
    unsigned                            ipv6only:1;
    unsigned                            ssl:1;
    unsigned                            http2:1;

    // current listener already closed.
    unsigned                            closed:1;

    int                                 sockfd;
    znx_address_t                       address;
    znx_event_handler_t                 accept_handler;

    // if socket fd transfer to other listener,
    // the handover will be set true.
    znx_atomic_bool_t                   handover;
    znx_atomic_int64_t                  refer;
    znx_atomic_int64_t                  conn_refer;
    znx_queue_t                         queue;
};


znx_bool_t znx_listener_open(znx_listener_t *l, int fd);

void znx_listener_close(znx_listener_t *l);


static inline void
znx_listener_init(znx_listener_t *l)
{
    l->sockfd = INVALID_FD;
    l->type = SOCK_STREAM;
}


static inline znx_listener_t *
znx_listener_alloc()
{
    znx_listener_t                      *l;
    l = znx_thread_calloc(sizeof(znx_listener_t));
    znx_listener_init(l);
    l->refer = 1;
    return l;
}


static inline void
znx_listener_conn_hold(znx_listener_t *l)
{
    znx_atomic_int64_add_fetch(&l->conn_refer, 1);
}


static inline void
znx_listener_conn_free(znx_listener_t *l)
{
    int64_t                     new_refer;
    new_refer = znx_atomic_int64_add_fetch(&l->conn_refer, -1);
    if (new_refer < 0) {
        ZNX_DEF_LOG_FATAL(NULL, "new_refer must be greater than or equal to 0");
    }

    if (new_refer != 0) {
        return;
    }

    if (znx_atomic_int64_load(&l->refer)) {
        return;
    }

    znx_thread_free(l);
}


static inline void
znx_listener_hold(znx_listener_t *l)
{
    znx_atomic_int64_add_fetch(&l->refer, 1);
}


static inline void
znx_listener_free(znx_listener_t *l)
{
    int64_t                             new_refer;
    new_refer = znx_atomic_int64_add_fetch(&l->refer, -1);
    if (new_refer < 0) {
        ZNX_DEF_LOG_FATAL(NULL, "new_refer must be greater than or equal to 0");
    }

    if (new_refer != 0) {
        return;
    }

    znx_listener_close(l);

    if (znx_atomic_int64_load(&l->conn_refer)) {
        return;
    }

    znx_thread_free(l);
}



typedef struct {
    znx_rwlock_t                        rwlock;
    znx_queue_t                         head;
    int64_t                             version;
} znx_listeners_t;

extern znx_listeners_t                  *g_listeners;

void znx_g_listeners_init();

void znx_g_listeners_close();

